---
slug: create-pages
title: createPages
description: The low-level routing API.
---

## Routing (low-level API)

The entry point for programmatic routing in Waku projects is `./src/server-entry.tsx`. Export the `createPages` function to create your layouts and pages.

`createLayout`, `createPage`, `createRoot`, and `createSlice` accept a configuration object to specify the route path, React component, and render method. Waku currently supports two options: `'static'` for static prerendering (SSG) or `'dynamic'` for server-side rendering (SSR).

For example, you can statically prerender a global header and footer in the root layout at build time, but dynamically render the rest of a home page at request time for personalized user experiences.

```tsx
// ./src/server-entry.tsx
import { createPages } from 'waku';

import { RootLayout } from './templates/root-layout';
import { HomePage } from './templates/home-page';
import { Root } from './components/root';
import { Slice } from './components/slice';

const pages = createPages(
  async ({ createPage, createLayout, createRoot, createSlice }) => [
    // Create root component
    // not required, but supported for customizing `<html>`, `<head>`, and `<body>` tags
    createRoot({
      render: 'static',
      component: Root,
    }),

    // Create root layout
    createLayout({
      render: 'static',
      path: '/',
      component: RootLayout,
    }),

    // Create home page
    createPage({
      render: 'dynamic',
      path: '/',
      component: HomePage,
    }),

    // Create slice
    createSlice({
      render: 'static',
      component: Slice,
      id: 'slice-1',
    }),
  ],
);

export default pages;
```

### Router Paths Type Safety

Waku provides inference for the router paths when created pages are returned from the callback passed into `createPages`. The following example shows how a minimal example for how to setup the router paths type safety.

```tsx
// ./src/server-entry.tsx
import { createPages } from 'waku';
import type { PathsForPages } from 'waku/router';

import { HomePage } from './templates/home-page';

const pages = createPages(async ({ createPage, createLayout }) => [
  // Create root layout
  createLayout({
    render: 'static',
    path: '/',
    component: RootLayout,
  }),

  // Create home page
  createPage({
    render: 'dynamic',
    path: '/',
    component: HomePage,
  }),
]);

declare module 'waku/router' {
  interface RouteConfig {
    paths: PathsForPages<typeof pages>;
  }
  interface CreatePagesConfig {
    pages: typeof pages;
  }
}

export default pages;
```

Once this is done, any `<Link />` component or hook from `waku/router` that uses paths in your app will use this type. In this case, the one valid use would be `<Link to="/" />`, but as you add more pages to the router, this type will grow to include them.

Note: The file-based router paths will be supported in the future with some form of code-generation to get the types from your local page files.

#### PageProps

The above snippet will also provide types for the `PageProps` type. This will give type safety for the slugs in your pages.

```tsx
// ./src/pages/about.tsx
import type { PageProps } from 'waku/router';

// PageProps<'/about/[foo]'> => { path: `/about/${string}`; foo: string; query: string; }
export default function AboutPage({ path }: PageProps<'/about/[foo]'>) {
  return <>{/* ...*/}</>;
}
```

### Pages

#### Single routes

Pages can be rendered as a single route (e.g., `/about`).

```tsx
// ./src/server-entry.tsx
import { createPages } from 'waku';

import { AboutPage } from './templates/about-page';
import { BlogIndexPage } from './templates/blog-index-page';

export default createPages(async ({ createPage }) => [
  // Create about page
  createPage({
    render: 'static',
    path: '/about',
    component: AboutPage,
  }),

  // Create blog index page
  createPage({
    render: 'static',
    path: '/blog',
    component: BlogIndexPage,
  }),
]);
```

#### Segment routes

Pages can also render a segment route (e.g., `/blog/[slug]`). The rendered React component automatically receives a prop named by the segment (e.g, `slug`) with the value of the rendered segment (e.g., `'introducing-waku'`). If statically prerendering a segment route at build time, a `staticPaths` array must also be provided.

**Note:** Slugs will be sanitized to replace spaces with `-`.

```tsx
// ./src/server-entry.tsx
import { createPages } from 'waku';

import { BlogArticlePage } from './templates/blog-article-page';
import { ProductCategoryPage } from './templates/product-category-page';

export default createPages(async ({ createPage }) => [
  // Create blog article pages
  // `<BlogArticlePage>` receives `slug` prop
  createPage({
    render: 'static',
    path: '/blog/[slug]',
    staticPaths: ['introducing-waku', 'introducing-create-pages'],
    component: BlogArticlePage,
  }),

  // Create product category pages
  // `<ProductCategoryPage>` receives `category` prop
  createPage({
    render: 'dynamic',
    path: '/shop/[category]',
    component: ProductCategoryPage,
  }),
]);
```

Static paths (or other values) could also be generated programmatically.

```tsx
// ./src/server-entry.tsx
import { createPages } from 'waku';

import { getBlogPaths } from './lib/get-blog-paths';
import { BlogArticlePage } from './templates/blog-article-page';

export default createPages(async ({ createPage }) => {
  const blogPaths = await getBlogPaths();

  return [
    createPage({
      render: 'static',
      path: '/blog/[slug]',
      staticPaths: blogPaths,
      component: BlogArticlePage,
    }),
  ];
});
```

#### Nested segment routes

Routes can contain multiple segments (e.g., `/shop/[category]/[product]`).

```tsx
// ./src/server-entry.tsx
import { createPages } from 'waku';

import { ProductDetailPage } from './templates/product-detail-page';

export default createPages(async ({ createPage }) => [
  // Create product detail pages
  // `<ProductDetailPage>` receives `category` and `product` props
  createPage({
    render: 'dynamic',
    path: '/shop/[category]/[product]',
    component: ProductDetailPage,
  }),
]);
```

For static prerendering of nested segment routes, the `staticPaths` array is instead composed of ordered arrays.

```tsx
// ./src/server-entry.tsx
import { createPages } from 'waku';

import { ProductDetailPage } from './templates/product-detail-page';

export default createPages(async ({ createPage }) => [
  // Create product detail pages
  // `<ProductDetailPage>` receives `category` and `product` props
  createPage({
    render: 'static',
    path: '/shop/[category]/[product]',
    staticPaths: [
      ['some-category', 'some-product'],
      ['some-category', 'another-product'],
    ],
    component: ProductDetailPage,
  }),
]);
```

#### Catch-all routes

Catch-all or "wildcard" routes (e.g., `/app/[...catchAll]`) have indefinite segments. Wildcard routes receive a prop with segment values as an ordered array.

For example, the `/app/profile/settings` route would receive a `catchAll` prop with the value `['profile', 'settings']`. These values can then be used to determine what to render in the component.

```tsx
// ./src/server-entry.tsx
import { createPages } from 'waku';

import { DashboardPage } from './templates/dashboard-page';

export default createPages(async ({ createPage }) => [
  // Create account dashboard
  // `<DashboardPage>` receives `catchAll` prop (string[])
  createPage({
    render: 'dynamic',
    path: '/app/[...catchAll]',
    component: DashboardPage,
  });
]);
```

### Layouts

Layouts wrap an entire route and its descendents. They must accept a `children` prop of type `ReactNode`. While not required, you will typically want at least a root layout.

#### Root layout

The root layout rendered at `path: '/'` is especially useful. It can be used for setting global styles, global metadata, global providers, global data, and global components, such as a header and footer.

```tsx
// ./src/server-entry.tsx
import { createPages } from 'waku';

import { RootLayout } from './templates/root-layout';

export default createPages(async ({ createLayout }) => [
  // Add a global header and footer
  createLayout({
    render: 'static',
    path: '/',
    component: RootLayout,
  }),
]);
```

```tsx
// ./src/templates/root-layout.tsx
import '../styles.css';

import { Providers } from '../components/providers';
import { Header } from '../components/header';
import { Footer } from '../components/footer';

export const RootLayout = async ({ children }) => {
  return (
    <Providers>
      <link rel="icon" type="image/png" href="/images/favicon.png" />
      <meta property="og:image" content="/images/opengraph.png" />
      <Header />
      <main>{children}</main>
      <Footer />
    </Providers>
  );
};
```

```tsx
// ./src/components/providers.tsx
'use client';

import { createStore, Provider } from 'jotai';

const store = createStore();

export const Providers = ({ children }) => {
  return <Provider store={store}>{children}</Provider>;
};
```

#### Other layouts

Layouts are also helpful further down the tree. For example, you could add a layout at `path: '/blog'` to add a sidebar to both the blog index and all blog article pages.

```tsx
// ./src/server-entry.tsx
import { createPages } from 'waku';

import { BlogLayout } from './templates/blog-layout';

export default createPages(async ({ createLayout }) => [
  // Add a sidebar to the blog index and blog article pages
  createLayout({
    render: 'static',
    path: '/blog',
    component: BlogLayout,
  }),
]);
```

```tsx
// ./src/templates/blog-layout.tsx
import { Sidebar } from '../components/sidebar';

export const BlogLayout = async ({ children }) => {
  return (
    <div className="flex">
      <div>{children}</div>
      <Sidebar />
    </div>
  );
};
```

### Root component

Root component is a special component that is rendered at the root of html document. It is useful for customizing `<html>`, `<head>`, and `<body>` tags.

```tsx
// ./src/components/root.tsx

export const Root = ({ children }) => {
  return (
    <html lang="en">
      <head></head>
      <body>{children}</body>
    </html>
  );
};
```

Add this with `createRoot` function inside `createPages`.

#### Note About `<head>`

If you only need to customize `<head>`, you can rely on React's [Support for Document Metadata](https://react.dev/blog/2024/04/25/react-19#support-for-metadata-tags) and do not need to use `createRoot`.

### Client entry point

The file `./src/server-entry.tsx` is the entry point for the server.
For the client, the entry point file is `./src/client-entry.tsx`.

The default client entry file content is the following.

```tsx
import { Component, StrictMode } from 'react';
import { createRoot, hydrateRoot } from 'react-dom/client';
import { Router } from 'waku/router/client';

const rootElement = (
  <StrictMode>
    <Router />
  </StrictMode>
);

if (globalThis.__WAKU_HYDRATE__) {
  hydrateRoot(document, rootElement);
} else {
  createRoot(document).render(rootElement);
}
```

You can omit `./src/client-entry.tsx` unless you need to modify it.

### Slices

Slice components are isolated components that can be rendered `static` or `dynamic` in their own right. This allows for patterns like adding `static` components wherever they'll compose nicely inside your `dynamic` or `static` layouts & pages. Or, a slice can be marked as `lazy` to be requested indepently from the rest of a particular page's components.

```tsx
// ./src/components/slice.tsx

export const Slice = () => {
  return (
    <div>
      <h2>Slice</h2>
    </div>
  );
};
```

Add this with `createSlice` function inside `createPages`.
